package com.rabbit.service.impl;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.rabbit.constant.ConfigKey;
import com.rabbit.constant.ResultCodeEnum;
import com.rabbit.constant.SecurityConstant;
import com.rabbit.db.entity.File;
import com.rabbit.db.entity.User;
import com.rabbit.db.entity.UserRoleMapping;
import com.rabbit.dto.OssFileDTO;
import com.rabbit.exception.BusinessException;
import com.rabbit.mapper.FileMapper;
import com.rabbit.mapper.UserMapper;
import com.rabbit.mapper.UserRoleMappingMapper;
import com.rabbit.redis.constant.RedisKeyConstant;
import com.rabbit.redis.service.RedisService;
import com.rabbit.service.OssService;
import com.rabbit.service.UserService;
import com.rabbit.vo.req.UserReq;
import com.rabbit.vo.res.UserDetailRes;
import com.rabbit.vo.res.UserRes;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author zjw
 * @date 2022/1/13 14:32
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserRoleMappingMapper userRoleMappingMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private OssService ossService;

    @Resource
    private FileMapper fileMapper;

    @Value("${upload.tmp.dir}")
    private String uploadTmpDir;

    @Override
    public void checkUnique(UserReq userReq, int operType) throws BusinessException {
        // 1. 登录账号唯一
        Example accountExample = new Example(User.class);
        Example.Criteria accountCriteria = accountExample.createCriteria();
        if (operType == 1) {
            accountCriteria.andEqualTo("loginAccount", userReq.getLoginAccount());
            if (userMapper.selectCountByExample(accountExample) > 0) {
                throw new BusinessException(ResultCodeEnum.USER_ACCOUNT_EXISTS_ERROR);
            }
        }
        // 2. 手机号唯一
        Example phoneExample = new Example(User.class);
        Example.Criteria phoneCriteria = phoneExample.createCriteria();
        phoneCriteria.andEqualTo("phone", userReq.getPhone());
        if (operType > 1) {
            phoneCriteria.andNotEqualTo("userId", userReq.getUserId());
        }
        if (userMapper.selectCountByExample(phoneExample) > 0) {
            throw new BusinessException(ResultCodeEnum.USER_PHONE_EXISTS_ERROR);
        }
        // 3. 邮箱唯一
        Example emailExample = new Example(User.class);
        Example.Criteria emailCriteria = emailExample.createCriteria();
        emailCriteria.andEqualTo("email", userReq.getEmail());
        if (operType > 1) {
            emailCriteria.andNotEqualTo("userId", userReq.getUserId());
        }
        if (userMapper.selectCountByExample(emailExample) > 0) {
            throw new BusinessException(ResultCodeEnum.USER_EMAIL_EXISTS_ERROR);
        }
    }

    @Override
    public PageInfo<UserRes> queryUserList(UserReq userReq) {
        User user = User.builder().build();
        BeanUtils.copyProperties(userReq, user);
        return PageHelper.startPage(userReq.getPage(), userReq.getLimit())
                .doSelectPageInfo(() -> userMapper.queryUserList(user));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBaseInfo(UserReq userReq) {
        User user = User.builder().build();
        BeanUtils.copyProperties(userReq, user);
        // 更新基本信息
        user.setUpdateBy(userReq.getOperatorUserName());
        userMapper.updateByPrimaryKeySelective(user);
        // 更新TokenSession中存储的用户信息
        user = userMapper.selectByPrimaryKey(user.getUserId());
        SaSession tokenSession = StpUtil.getTokenSession();
        tokenSession.set(SecurityConstant.LOGIN_USER, user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(UserReq userReq) {
        Long userId = userReq.getUserId();
        // 删除用户及角色关联
        userMapper.deleteByPrimaryKey(userId);
        userRoleMappingMapper.delete(UserRoleMapping.builder().userId(userId).build());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(UserReq userReq) {
        Long userId = userReq.getUserId();
        // 更新密码
        User user = User.builder().userId(userId)
                .salt(IdUtil.fastSimpleUUID())
                .updateBy(userReq.getOperatorUserName())
                .updateTime(DateUtil.date()).build();
        user.setPassword(DigestUtil.md5Hex(user.getSalt() + userReq.getPassword()));
        userMapper.updateByPrimaryKeySelective(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(UserReq userReq) {
        User user = new User();
        BeanUtils.copyProperties(userReq, user);
        // 把UUID当作盐，加密，新用户默认密码从配置中获取
        user.setIsEnabled(true);
        user.setSalt(IdUtil.fastSimpleUUID());
        user.setPassword(DigestUtil.md5Hex(user.getSalt() + redisService.hGet(RedisKeyConstant.SYS_CONFIG, ConfigKey.USER_INIT_PWD)));
        user.setAvatar(redisService.hGet(RedisKeyConstant.SYS_CONFIG, ConfigKey.USER_INIT_AVATAR) + "");
        user.setCreateBy(userReq.getOperatorUserName());
        // 添加用户，用户与角色关联
        userMapper.insertSelective(user);
        insertUserRole(user.getUserId(), userReq.getRoleIds());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(UserReq userReq) {
        Long userId = userReq.getUserId();
        // 删除用户与角色关联
        UserRoleMapping userRoleMapping = UserRoleMapping.builder().userId(userId).build();
        userRoleMappingMapper.delete(userRoleMapping);
        // 保存用户与角色关联
        insertUserRole(userId, userReq.getRoleIds());
        // 更新用户
        User user = User.builder().build();
        BeanUtils.copyProperties(userReq, user);
        user.setSalt(IdUtil.fastSimpleUUID());
        user.setPassword(DigestUtil.md5Hex(user.getSalt() + userReq.getPassword()));
        user.setUpdateBy(userReq.getOperatorUserName());
        userMapper.updateByPrimaryKeySelective(user);
        // 去除TokenSession中缓存的角色信息
        SaSession saSession = StpUtil.getSessionByLoginId(userId, false);
        if (saSession != null) {
            List<TokenSign> tokenSignList = saSession.getTokenSignList();
            tokenSignList.forEach(e -> {
                SaSession tokenSession = StpUtil.getTokenSessionByToken(e.getValue());
                tokenSession.delete(SecurityConstant.LOGIN_ROLE);
            });
        }
    }

    @Override
    public void checkIsAdmin(Long userId) throws BusinessException {
        UserRoleMapping userRoleMapping = UserRoleMapping.builder().userId(userId).build();
        List<UserRoleMapping> mappings = userRoleMappingMapper.select(userRoleMapping);
        for (UserRoleMapping mapping : mappings) {
            if (mapping.getRoleId().equals(1L)) {
                throw new BusinessException(ResultCodeEnum.USER_ADMIN_UPDATE_ERROR);
            }
        }
    }

    @Override
    public UserDetailRes queryUserById(Long userId) {
        User user = userMapper.selectByPrimaryKey(userId);
        UserDetailRes detailRes = UserDetailRes.builder().build();
        BeanUtils.copyProperties(user, detailRes);
        List<UserRoleMapping> roleIds = userRoleMappingMapper.select(UserRoleMapping.builder().userId(userId).build());
        detailRes.setRoleIds(roleIds.stream().map(UserRoleMapping::getRoleId).collect(Collectors.toList()));
        return detailRes;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void checkDataScope(Long userId) throws BusinessException {
        List<String> userRoles = (List<String>) StpUtil.getTokenSession().get(SecurityConstant.LOGIN_ROLE);
        if (!userRoles.contains(SecurityConstant.SUPER_ADMIN)) {
            // 非超级管理员 只能查看自己的用户信息
            Long loginUserId = (Long) StpUtil.getLoginId();
            if (!Objects.equals(userId, loginUserId)) {
                throw new BusinessException(ResultCodeEnum.USER_DETAIL_PER_ERROR);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserEnabled(UserReq userReq) {
        User user = User.builder().userId(userReq.getUserId()).isEnabled(userReq.getIsEnabled()).updateBy(userReq.getOperatorUserName()).build();
        userMapper.updateByPrimaryKeySelective(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String updateAvatar(UserReq userReq) throws BusinessException {
        // 1. Base64到本地图片
        String fileUrl = base64ToFile(userReq.getAvatar());
        // 2. 本地图片上传至OSS
        if (StringUtils.hasLength(fileUrl) && FileUtil.exist(fileUrl)) {
            OssFileDTO ossFileDTO = ossService.uploadAvatar(userReq.getUserId(), fileUrl);
            // 3. 删除原头像文件及OSS文件
            User user = userMapper.selectByPrimaryKey(userReq.getUserId());
            String defaultAvatar = redisService.hGet(RedisKeyConstant.SYS_CONFIG, ConfigKey.USER_INIT_AVATAR) + "";
            if (!Objects.equals(user.getAvatar(), defaultAvatar)) {
                File file = File.builder().fileUrl(user.getAvatar()).build();
                fileMapper.delete(file);
                ossService.deleteObject(user.getAvatar());
            }
            // 4. 更新用户头像
            File file = File.builder().build();
            BeanUtils.copyProperties(ossFileDTO, file);
            file.setCreateTime(ossFileDTO.getUploadTime());
            file.setCreateBy(userReq.getOperatorUserName());
            fileMapper.insertSelective(file);
            user = User.builder().userId(userReq.getUserId()).avatar(file.getFileUrl()).build();
            userMapper.updateByPrimaryKeySelective(user);
            // 5. 删除临时文件
            FileUtil.del(fileUrl);
            return ossService.packageAvatarUrl(file.getFileUrl());
        } else {
            log.error("updateAvatar - 本地图片不存在，fileUrl:{}", fileUrl);
            throw new BusinessException(ResultCodeEnum.USER_AVATAR_UPDATE_ERROR);
        }
    }

    /**
     * base64转本地临时图片
     *
     * @param base64 头像base64
     * @return 文件路径
     */
    private String base64ToFile(String base64) {
        int index = base64.indexOf(";base64,");
        if (index != -1) {
            base64 = base64.substring(index + 8);
        }
        byte[] imageByte;
        OutputStream out;
        String fileUrl = uploadTmpDir + java.io.File.separator + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmssSSS") + ".png";
        try {
            imageByte = Base64.decode(base64);
            for (int i = 0; i < imageByte.length; ++i) {
                if (imageByte[i] < 0) {
                    imageByte[i] += 256;
                }
            }
            out = new FileOutputStream(fileUrl);
            out.write(imageByte);
            out.flush();
            out.close();
            return fileUrl;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 新增用户角色信关联
     *
     * @param userId  用户ID
     * @param roleIds 角色组
     */
    private void insertUserRole(Long userId, List<Long> roleIds) {
        List<UserRoleMapping> userRoleMappings = new ArrayList<>();
        roleIds.forEach(e -> {
            userRoleMappings.add(UserRoleMapping.builder().roleId(e).userId(userId).build());
        });
        userRoleMappingMapper.insertList(userRoleMappings);
    }

    public static void main(String[] args) {
        String salt = IdUtil.fastSimpleUUID();
        System.out.println(salt);
        System.out.println(DigestUtil.md5Hex(salt + "admin"));
    }
}
